Plugin Architecture for Demo Systems, and not only
Icebreaker
Intro
Recently I've been playing around with this stuff, so I've quickly thrown this together, maybe it will be a good starting point for someone, of course this assumes a certain level of general programming knowledge, but whatever ...
The Plan
Let's say that we want to implement multiple image support for textures, and each "reader/writer" would be an separate plugin, so you could just throw in a plugin and add support to a new image format.
To load/unload DLLs dynamically use LoadLibrary, and FreeLibrary, and you get the address of a function inside a DLL with GetProcAdress under Windows. So, our DLLs will have to export at least 3 functions. Of course, this can be extended to return data about the plugin version, etc. Here we just keep it simple.
- init
- getinstance
- shutdown
Also, we will have an class with pure virtual functions like this (this is just a very very basic example):
class Image
{
Image(){};
virtual ~Image(){};
virtual bool load( const char *filename ) = 0;
virtual bool save( const char *filename )= 0;
virtual char * getData() = 0;
virtual void setData( char *data ) = 0;
};
Basically all our Image Loaders will be derived from this class, implementing the necessary functions.
class TGAImage : public Image
{
...
(implementation omitted for clearness)
};
The Implementation
Now all let's talk a bit about our exported functions.
In the "init" we will initialize stuff, we can pass pointers to the plugin, etc. With the getinstance you can get a new instance of the contained class, in our case TGAImage. If the class is singleton then getinstance will always return the same instance, else it will create a new instance at every call. In the shutdown we release stuff, etc.
The best thing is to scan a folder at the application startup, and load all the 'plugins' inside a plugin manager for easier management. So to make use of a plugin, we do the following things:
typedef void (*init_func)(void);
typedef void (*getinstance_func)(void);
typedef void (*shutdown_func)(void);
init_func init = NULL;
getinstance_func getinstance = NULL;
shutdown_func shutdown = NULL;
HANDLE hModule = LoadLibrary("tgareader.dll");
if(hModule)
{
init = GetProcAddress( hModule, "init" );
getinstance = GetProcAddress( hModule, "getinstance" );
shutdown = GetProcAddress( hModule, "shutdown" );
}
if(!init || !getinstance || !shutdown)
{
// error, it's not a valid plugin or an error occurred
return 1;
}
init();
Image *img = getinstance();
img->load( "/floor/wood.tga" );
...
delete img;
shutdown();
FreeLibrary( hModule );
Notes
I guess this was pretty straightforward without writing tons of code, or anything special or HARD. Of course the getinstance could be customized to pass parameters directly to the constructor, etc, but this can be done easily, now that we have seen the concept.
Normally we will wrap the plugin manager into a singleton class, which will be available in our program modules for easy access. Even better if we make a function like FindImage(), which takes care, and chooses the correct reader by looking at the extension, and stuff.
Like I always said the possibilities are endless, you just need to have the "design" in your head :)) We could do plugins for our whole demo system. For example we could have a DirectX and OpenGL renderer, or sound/music with DirectSound and BASS or FMOD. We would just have to code an additional DLL, without recompiling everything. This also makes patching a lot easier, since all you have to do is to change some DLL.
Sometimes, it's also good to have some sort of Environment, where you can store/get stuff at runtime, from all your modules, even DLLs.
Linux users can do similar stuff, but they will use Shared Objects instead, the procedure is more or less the same. If we have an architecture like this, it will be piss easy to do an Editor with WinForms using C# or VB.NET, since we could make use of our code right away, re-writing only small portions.
Outro
Pretty much that's it, have fun coding your own plugin architecture, and remember to keep your code as portable as possible. :))
May the source be with you!
Any comments, suggestions, insights are welcome :)
Icebreaker